package vip.liux.backend.application.serviceImpl.files;

import org.springframework.data.domain.Page;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import vip.liux.application.dots.ListResultDto;
import vip.liux.application.dots.PagedResultDto;
import vip.liux.backend.application.service.files.AutoMapperProfile;
import vip.liux.backend.application.service.files.FileDescriptorAppService;
import vip.liux.backend.application.service.files.dtos.*;
import vip.liux.backend.infrastructure.constants.FileDescriptorConsts;
import vip.liux.contracts.models.files.FileDescriptor;
import vip.liux.contracts.models.files.FileManager;
import vip.liux.contracts.models.files.FileNameValidator;
import vip.liux.contracts.models.files.NotEnoughStorageSizeException;
import vip.liux.contracts.repositories.files.FileDescriptorRepository;
import vip.liux.contracts.shared.kits.StorageKit;
import vip.liux.contracts.shared.properties.FilesProperties;
import vip.liux.core.constant.GlobalConstant;
import vip.liux.core.content.IRemoteStreamContent;
import vip.liux.core.content.RemoteStreamContent;
import vip.liux.core.exception.SystemException;
import vip.liux.core.exception.UserFriendlyException;

import java.io.IOException;
import java.io.Serializable;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;

import static vip.liux.application.PageKit.toPage;

/**
 * 文件描述符应用服务实现类
 * <p>提供文件的创建、重命名、下载、删除等操作。</p>
 */
@Service
public class FileDescriptorAppServiceImpl implements FileDescriptorAppService {

    // 文件管理器，用于处理文件的具体操作（如创建、删除、重命名等）
    protected final FileManager fileManager;

    // 文件描述符仓库，用于与数据库交互，存储和检索文件描述符信息
    protected final FileDescriptorRepository fileDescriptorRepository;

    // Redis 缓存，用于存储文件下载令牌
    protected final RedisTemplate<Object, Object> downloadTokenCache;

    // 文件属性配置，包含文件大小限制等配置信息
    protected final FilesProperties filesProperties;

    /**
     * 构造方法
     *
     * @param fileManager              文件管理器
     * @param fileDescriptorRepository 文件描述符仓库
     * @param downloadTokenCache       下载令牌缓存
     * @param filesProperties          文件属性配置
     */
    public FileDescriptorAppServiceImpl(FileManager fileManager, FileDescriptorRepository fileDescriptorRepository, RedisTemplate<Object, Object> downloadTokenCache, FilesProperties filesProperties) {
        this.fileManager = fileManager;
        this.fileDescriptorRepository = fileDescriptorRepository;
        this.downloadTokenCache = downloadTokenCache;
        this.filesProperties = filesProperties;
    }

    /**
     * 根据文件 ID 获取文件描述符
     *
     * @param id 文件 ID
     * @return 文件描述符 Dto
     */
    @Override
    public FileDescriptorDto get(Long id) {
        // 从数据库中查找文件描述符，如果不存在则抛出异常
        FileDescriptor fileDescriptor = fileDescriptorRepository.findById(id).orElseThrow();

        // 使用 AutoMapper 将文件描述符实体转换为 Dto
        return AutoMapperProfile.INSTANCE.toFileDescriptorDto(fileDescriptor);
    }

    /**
     * 获取指定目录下的文件描述符列表
     *
     * @param directoryId 目录 ID
     * @return 文件描述符列表 Dto
     */
    @Override
    public ListResultDto<FileDescriptorDto> getList(Long directoryId) {
        // 从数据库中获取指定目录下的文件列表
        List<FileDescriptor> list = fileDescriptorRepository.getList(directoryId, null, null);

        // 将文件列表转换为 Dto 列表并返回
        return new ListResultDto<>(AutoMapperProfile.INSTANCE.toFileDescriptorDtoList(list));
    }

    /**
     * 重命名文件
     *
     * @param id    文件 ID
     * @param input 重命名输入参数
     * @return 重命名后的文件描述符 Dto
     */
    @Override
    public FileDescriptorDto rename(Long id, RenameFileInput input) {
        // 查找文件，如果不存在则抛出异常
        FileDescriptor file = fileDescriptorRepository.findById(id).orElseThrow();

        // 调用文件管理器进行重命名操作
        fileManager.rename(file, input.name());

        // 保存重命名后的文件信息到数据库
        fileDescriptorRepository.save(file);

        // 返回重命名后的文件描述符 Dto
        return AutoMapperProfile.INSTANCE.toFileDescriptorDto(file);
    }

    /**
     * 下载文件
     *
     * @param id    文件 ID
     * @param token 下载令牌
     * @return 远程流内容
     */
    @Override
    public IRemoteStreamContent download(Long id, String token) {
        // 从 Redis 缓存中获取下载令牌
        FileDownloadTokenCacheItem downloadToken = (FileDownloadTokenCacheItem) downloadTokenCache.opsForValue().get(token);

        // 验证下载令牌是否有效
        if (downloadToken == null || !Objects.equals(downloadToken.FileDescriptorId(), id)) {
            throw new SystemException("invalid download token:" + token);
        }

        // 查找文件，如果不存在则抛出异常
        FileDescriptor file = fileDescriptorRepository.findById(id).orElseThrow();

        // 返回文件的远程流内容
        // todo get content to storage
        return new RemoteStreamContent(StorageKit.getInputStream(file.getId().toString()), file.getName(), file.getMimeType(), file.getSize().longValue(), true);
    }

    /**
     * 删除文件
     *
     * @param id 文件 ID
     */
    @Override
    public void delete(Long id) {
        // 查找文件，如果不存在则抛出异常
        FileDescriptor file = fileDescriptorRepository.findById(id).orElseThrow();

        // 调用文件管理器删除文件
        fileManager.delete(file);
    }

    /**
     * 创建文件
     *
     * @param directoryId     目录 ID
     * @param inputWithStream 创建文件输入参数（包含文件流）
     * @return 创建的文件描述符 Dto
     */
    @Override
    public FileDescriptorDto create(Long directoryId, CreateFileInputWithStream inputWithStream) {
        // 检查文件大小是否超出限制
        checkSize(inputWithStream.file().getContentLength());

        try {
            // 读取文件内容为字节数组
            byte[] bytes = inputWithStream.file().getInputStream().readAllBytes();

            // 调用文件管理器创建文件
            FileDescriptor file = fileManager.create(inputWithStream.name(), inputWithStream.file().getContentType(), bytes, directoryId, null, false);

            // 返回创建的文件描述符 Dto
            return AutoMapperProfile.INSTANCE.toFileDescriptorDto(file);
        } catch (IOException e) {
            throw new SystemException("Failed to read file content", e);
        }
    }

    /**
     * 移动文件
     *
     * @param input 移动文件输入
     * @return 移动后的文件描述符
     */
    @Override
    public ListResultDto<FileDescriptorDto> move(MoveFileInput input) {
        return new ListResultDto<>(input.ids().stream().map(id -> {
            FileDescriptor fileDescriptor = fileDescriptorRepository.findById(id).orElseThrow();

            fileManager.move(fileDescriptor, input.newDirectoryId());
            fileDescriptorRepository.save(fileDescriptor);

            return AutoMapperProfile.INSTANCE.toFileDescriptorDto(fileDescriptor);
        }).toList());

    }

    /**
     * 获取文件上传预信息
     *
     * @param input 文件上传预信息请求列表
     * @return 文件上传预信息列表
     */
    @Override
    public List<FileUploadPreInfoDto> getPreInfo(List<FileUploadPreInfoRequest> input) {

        long totalSize = input.stream().map(FileUploadPreInfoRequest::size).reduce(0L, Long::sum);

        checkSize(totalSize);

        List<FileUploadPreInfoDto> result = new ArrayList<>();

        for (FileUploadPreInfoRequest infoRequest : input) {
            result.add(new FileUploadPreInfoDto(
                    infoRequest.fileName(),
                    fileDescriptorRepository.findByName(infoRequest.fileName(), infoRequest.directoryId()) != null,
                    FileNameValidator.isValidName(infoRequest.fileName())
            ));
        }

        return result;
    }

    /**
     * 获取文件内容
     *
     * @param id 文件ID
     * @return 文件内容字节数组
     */
    @Override
    public byte[] getContent(Long id) {
        // todo get content to storage
        return StorageKit.getContent(id.toString());
    }

    /**
     * 获取文件下载令牌
     *
     * @param id 文件ID
     * @return 下载令牌结果
     */
    @Override
    public DownloadTokenResultDto getDownloadToken(Long id) {
        String token = UUID.randomUUID().toString();

        downloadTokenCache.opsForValue().setIfAbsent(token, new FileDownloadTokenCacheItem(id), Duration.ofSeconds(60));

        return new DownloadTokenResultDto(token);
    }

    /**
     * 获取目录下的文件分页列表
     *
     * @param input 查询目录输入
     * @return 分页结果
     */
    @Override
    public PagedResultDto<FileDescriptorDto> getPageList(QueryDirectoryInput input) {

        // 从数据库中获取指定目录下的文件列表
        Page<FileDescriptor> list = fileDescriptorRepository.getPage(
                input.getDirectoryId(),
                input.getFileType(),
                input.getName(),
                toPage(input)
        );

        // 将文件列表转换为 Dto 列表并返回
        return new PagedResultDto<>(AutoMapperProfile.INSTANCE.toFileDescriptorDtoList(list.getContent()), list.getTotalElements());
    }

    @Override
    public void batchDelete(List<Long> ids) {
        ids.forEach(this::delete);
    }

    /**
     * 检查文件大小是否超出限制
     *
     * @param contentLength 文件内容长度
     */
    protected void checkSize(long contentLength) {
        if (contentLength > FileDescriptorConsts.MAX_SIZE_LENGTH) {
            throw new UserFriendlyException("FileTooBig: " + beautifySize(FileDescriptorConsts.MAX_SIZE_LENGTH));
        }

        String maxStorageSize = filesProperties.getMaxStorageSize();

        if (maxStorageSize == null || maxStorageSize.equals("0")) {
            return;
        }

        long maxSize = Long.parseLong(maxStorageSize);
        long totalStorage = fileDescriptorRepository.getTotalSize();

        if ((totalStorage + contentLength) < maxSize) {
            return;
        }

        long remainedSize = (maxSize - totalStorage);
        throw new NotEnoughStorageSizeException(beautifySize(maxSize), beautifySize(remainedSize));
    }

    /**
     * 美化文件大小显示
     *
     * @param size 文件大小
     * @return 美化后的文件大小字符串
     */
    protected String beautifySize(long size) {
        if (size == 0 || size == 1) {
            return size + " Byte";
        }

        if (size >= GlobalConstant.TERABYTE) {
            float fixedSize = (float) size / GlobalConstant.TERABYTE;
            return formatSize(fixedSize) + " TB";
        }

        if (size >= GlobalConstant.GIGABYTE) {
            float fixedSize = (float) size / GlobalConstant.GIGABYTE;
            return formatSize(fixedSize) + " GB";
        }

        if (size >= GlobalConstant.MEGABYTE) {
            float fixedSize = (float) size / GlobalConstant.MEGABYTE;
            return formatSize(fixedSize) + " MB";
        }

        if (size >= GlobalConstant.KILOBYTE) {
            float fixedSize = (float) size / GlobalConstant.KILOBYTE;
            return formatSize(fixedSize) + " KB";
        }

        return size + " B";
    }

    /**
     * 格式化文件大小
     *
     * @param size 文件大小
     * @return 格式化后的文件大小字符串
     */
    protected String formatSize(float size) {
        String s = String.format("%.2f", size);
        return s.endsWith("00") ? String.valueOf((int) size) : s;
    }

    /**
     * 文件下载令牌缓存项
     */
    public record FileDownloadTokenCacheItem(Long FileDescriptorId) implements Serializable {
    }
}